package com.pinfly.common.config;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.pinfly.common.crypto.CryptoFactory;
import com.pinfly.common.exception.MSException;
import com.pinfly.common.util.StringUtil;

/**
 * This utility class is used to generate an upload file or import file from the
 * xml properties files. It is also used as part of the build process.
 * 
 */
public class PfyXMLUtil
{
    private static final Logger logger = Logger.getLogger (PfyXMLUtil.class);

    private static final String TYPE_ELEMENT = "type";
    private static final String ATTR_MIN = "min";
    private static final String ATTR_MAX = "max";
    private static final String NAME_ELEMENT = "name";
    private static final String IMPORT = "import";
    private static final String GROUP_ELEMENT = "group";
    private static final String COMMAND_ELEMENT = "command";
    private static final String VALUE_ELEMENT = "value";
    private static final String DESC_ELEMENT = "description";
    private static final String PARAMETER_ELEMENT = "parameter";
    private static final String CUSTOMER_FLAG = "custFlag";
    private static final String ENCRYPTED_FLAG = "encrypted";
    private static final String RULES_ELEMENT = "rules";
    private static final String RULE_ELEMENT = "rule";
    private static final String OPTIONS_ELEMENT = "options";
    private static final String OPTION_ELEMENT = "option";
    private static final String ACTION_ELEMENT = "action";
    private static final String HLN_ELEMENT = "hln";
    private static final String NUMBER_ELEMENT = "number";

    private static final String ACTION_ADD = "add";
    private static final String BOOLEAN_TRUE = "true";

    private static final String TITLE_ELEMENT = "title";
    private static final String BODY_ELEMENT = "body";
    private static final String MUTABILITY_ELEMENT = "mutability";
    private static final String STANDARD_MUTABILITY = "standard";
    private static final String OPENING_TAG = "<";
    private static final String ENDING_TAG = ">";
    private static final String CLOSING_TAG = "</";

    private static final String OPEN_BODY_TAG = "<body>";
    private static final String END_BODY_TAG = "</body>";

    private static final String OPEN_TITLE_TAG = "<title>";
    private static final String END_TITLE_TAG = "</title>";

    private static final String OUTFILE_NAME = "install";
    private static final String OUTIMPORT_NAME = "import";
    private static final String XML_FILE_EXT = ".xml";

    private static final String SPACE4 = "    ";
    private static final String SPACE8 = "        ";
    private static final String SPACE12 = "            ";
    private static final String SPACE16 = "                ";

    private static final String COMPONENT_ATTR = "component";
    private static final String VERSION_ATTR = "version";

    private static final String START_CDATA = "<![CDATA[";
    private static final String END_CDATA = "]]>";

    private static final String LS = System.getProperty ("line.separator");
    private static final PfyXMLUtil s_instance = new PfyXMLUtil ();

    /**
     * Hide the default constructor
     */
    private PfyXMLUtil ()
    {
    }

    public static PfyXMLUtil getInstance ()
    {
        return s_instance;
    }

    /**
     * Returns a boolean indicating if the group is known.
     * 
     * @param gnode
     * @param groupName
     * @return true if valid
     */
    private boolean isValidGroup (Element gnode, String groupName)
    {
        boolean isValid = false;
        Node pn = gnode.getParentNode ();

        NodeList nn = pn.getChildNodes ();
        int le = nn.getLength ();
        for (int i = 0; i < le; i++)
        {
            Node nnn = nn.item (i);
            String name = getNodeValue (nnn, NAME_ELEMENT);
            if (name.equalsIgnoreCase (groupName))
            {
                isValid = true;
                break;
            }
        }
        return isValid;
    }

    /**
     * Returns the XML node value
     * 
     * @param pnode
     * @param tag
     * @return node value
     */
    private String getNodeValue (Node pnode, String tag)
    {
        String ret = "";
        NodeList nl = pnode.getChildNodes ();
        for (int i = 0; i < nl.getLength (); i++)
        {
            Node node = nl.item (i);
            if (node.getNodeName ().equalsIgnoreCase (tag))
            {
                Node n = node.getFirstChild ();
                if (n != null)
                {
                    ret = n.getNodeValue ();
                }
                return ret;
            }
        }
        return ret;
    }

    /**
     * To retrieve properties from a xml properties file in local file system
     * 
     * @param filepath
     * @param group
     * @return the properties in the file
     * @exception ConfigException
     */
    public PfyProperties readXMLPropertiesFile (String filepath, String group) throws ConfigException
    {

        if (logger.isTraceEnabled ())
        {
            logger.trace (new Object[]
            { "filepath=" + filepath, "group=" + group });
        }

        if (StringUtil.isNullOrBlank (filepath))
        {
            throw new IllegalArgumentException ("filepath can't be null or empty string.");
        }

        if (StringUtil.isNullOrBlank (group))
        {
            throw new IllegalArgumentException ("group can't be null or empty string.");
        }

        try
        {
            File file = new File (filepath);
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance ();
            dbf.setIgnoringComments (true);
            DocumentBuilder db = dbf.newDocumentBuilder ();
            Document dom = db.parse (file);

            // check the group name in properties file
            Element groupNode = dom.getDocumentElement ();

            if (!isValidGroup (groupNode, group))
            {
                throw new IllegalArgumentException ("an incorrect group name is in xml file=" + filepath);
            }
            // retrieve the properties from its parameter command block
            NodeList nodes = groupNode.getElementsByTagName (PARAMETER_ELEMENT);
            if (nodes == null || nodes.getLength () == 0)
            {
                throw new IllegalArgumentException ("Unable to find the parameter in file=" + filepath);
            }

            PfyProperties prop = new PfyProperties (group);
            for (int i = 0; i < nodes.getLength (); i++)
            {
                Node nd = nodes.item (i);
                String name = getNodeValue (nd, NAME_ELEMENT);
                String value = getNodeValue (nd, VALUE_ELEMENT);

                String encrypted = getNodeValue (nd, ENCRYPTED_FLAG);
                try
                {
                    if (encrypted.equalsIgnoreCase (BOOLEAN_TRUE))
                    {
                        value = CryptoFactory.getInstance ().decrypt (value);
                    }
                }
                catch (Exception e1)
                {
                    // unable to decrypt value, just log a message and use the
                    // value.
                    logger.warn ("Unable to decrypt parameter name=" + name + " just the specified value=" + value, e1);
                }

                prop.put (name, value);
            }

            if (logger.isTraceEnabled ())
            {
                logger.trace ("Found properties for group=" + group + " in filepath=" + filepath);
            }

            return prop;
        }
        catch (Exception e)
        {
            String error = "Unable to parse xml file for group=" + group + " using filepath=" + filepath;
            logger.warn (error, e);
            throw new ConfigException (error, e);
        }
    }

    /**
     * Create an import xml file. The generated file will be called import.xml
     * and will be placed in the directory pointed to by the destPath parameter.
     * If the import.xml file exists it will be overwritten.
     * 
     * @param srcPath The folder which contains the property.xml files
     * @param destPath The folder where the import.xml will be placed
     * @return The number of bytes written to the import.xml file.
     * @throws MSException If any issues were encountered generating import file
     *             If there were no property.xml files in the srcPath folder
     * @throws IllegalArgumentException If srcPath is null or empty If destPath
     *             is null or empty
     */
    public int createImportXMLFile (String srcPath, String destPath) throws MSException
    {
        if (StringUtil.isNullOrBlank (srcPath))
        {
            throw new IllegalArgumentException ("srcPath can't be null or empty string.");
        }

        if (StringUtil.isNullOrBlank (destPath))
        {
            throw new IllegalArgumentException ("destPath can't be null or empty string.");
        }

        int length = 0;
        // retrieve all of xml files
        String[] srcFiles = getSrcXMLFileName (srcPath);
        if (srcFiles.length > 0)
        {
            String[] srcStrs = convertCDATADescription (srcFiles, srcPath);
            length = generateImportXMLFile (srcStrs, destPath);
        }
        else
        {
            throw new MSException ("Unable to find any xml files in srcPath=" + srcPath);
        }

        return length;

    }

    /**
     * To create one install.xml for installation at system and system.customer
     * node if the parameter's custFlag is true, the parameter will be added to
     * the customer node at the same group.
     * 
     * @param srcPath
     * @param destPath
     * @param component
     * @param version
     * @return the number of bytes wrote to the install.xml file
     * @throws MSException If any issues were encountered generating import file
     *             If there were no property.xml files in the srcPath folder
     * @throws IllegalArgumentException If srcPath is null or empty If destPath
     *             is null or empty
     */
    private int createInstallXMLFile (String srcPath, String destPath, String component, String version)
                                                                                                        throws MSException
    {
        if (StringUtil.isNullOrBlank (srcPath))
        {
            throw new IllegalArgumentException ("srcPath can't be null or empty string.");
        }

        if (StringUtil.isNullOrBlank (destPath))
        {
            throw new IllegalArgumentException ("destPath can't be null or empty string.");
        }

        if (StringUtil.isNullOrBlank (component))
        {
            throw new IllegalArgumentException ("component can't be null or empty string.");
        }

        if (StringUtil.isNullOrBlank (version))
        {
            throw new IllegalArgumentException ("version can't be null or empty string.");
        }

        int length = 0;
        // retrieve all of xml files
        String[] srcFiles = getSrcXMLFileName (srcPath);
        if (srcFiles.length > 0)
        {
            String[] srcStrs = convertCDATADescription (srcFiles, srcPath);
            length = generateInstallXMLFile (srcStrs, destPath, component, version);
        }
        else
        {
            throw new MSException ("Unable to find any xml files in srcPath=" + srcPath);
        }

        return length;
    }

    /**
     * To create a install.xml file
     * 
     * @param srcString
     * @param destPath
     * @return the number of bytes wrote to install file
     * @throws MSException
     */
    private int generateImportXMLFile (String[] srcString, String destPath) throws MSException
    {
        try
        {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance ();
            dbf.setIgnoringComments (true);
            DocumentBuilder db = dbf.newDocumentBuilder ();

            StringBuilder buff = new StringBuilder ();
            Map <CfgXMLGroup, List <CfgXMLParameter>> groups = new HashMap <CfgXMLGroup, List <CfgXMLParameter>> ();
            for (int i = 0; i < srcString.length; i++)
            {
                StringBuilder buffer = new StringBuilder (srcString[i]);
                ByteArrayInputStream bis = new ByteArrayInputStream (buffer.toString ().getBytes ("UTF-8"));
                Document dom = db.parse (bis);
                Element el = dom.getDocumentElement ();

                String name = getNodeValue (el, NAME_ELEMENT);
                String[] desc = getNodeDescriptionValue (el, DESC_ELEMENT);
                buff.append (SPACE4).append (makeStartElement (COMMAND_ELEMENT)).append ("\n");

                buff.append (addGroup (null, name, desc));

                List <CfgXMLParameter> parameters = new ArrayList <CfgXMLParameter> ();
                NodeList params = el.getElementsByTagName (PARAMETER_ELEMENT);
                for (int j = 0; j < params.getLength (); j++)
                {
                    Node nd = params.item (j);
                    String pname = getNodeValue (nd, NAME_ELEMENT);
                    String[] pdesc = getNodeDescriptionValue (nd, DESC_ELEMENT);
                    String pvalue = getNodeValue (nd, VALUE_ELEMENT);
                    String pcusFlag = getNodeValue (nd, CUSTOMER_FLAG);
                    String mutability = getNodeValue (nd, MUTABILITY_ELEMENT);
                    String encryptFlag = getNodeValue (nd, ENCRYPTED_FLAG);
                    String[] rules = getNodeRules (nd, RULES_ELEMENT);
                    List <String> options = getNodeOptions (nd, OPTIONS_ELEMENT);
                    long min = 0;
                    long max = 0;
                    if (rules.length == 3)
                    {
                        min = Long.parseLong (rules[1]);
                        max = Long.parseLong (rules[2]);
                    }

                    // add parameter
                    String group = name;

                    StringBuilder param = addParameter (null, group, pname, pvalue, pdesc, null, mutability,
                                                        encryptFlag, rules[0], min, max, options);
                    buff.append (param).append ("\n");

                }
                buff.append (SPACE4).append (makeEndElement (COMMAND_ELEMENT)).append ("\n").append ("\n");

                if (parameters.size () > 0)
                {
                    CfgXMLGroup xgroup = new CfgXMLGroup (name, desc);
                    groups.put (xgroup, parameters);
                }
            }

            // now generate the completed install.xml file
            // file header
            StringBuilder buffer = new StringBuilder ();
            buffer.append ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>").append ("\n");
            buffer.append (getCopyRight ()).append ("\n");

            buffer.append (makeFirstCommandElement (COMMAND_ELEMENT)).append ("\n");
            buffer.append (SPACE4).append (makeElement (TYPE_ELEMENT, IMPORT)).append ("\n");

            buffer.append (buff);

            buffer.append (makeEndElement (COMMAND_ELEMENT)).append ("\n");

            String data = convertCDATADescription (buffer.toString ());

            // write to destination folder
            File f = new File (destPath);
            if (!f.exists ())
            {
                f.mkdir ();
            }

            String filepath = destPath + System.getProperty ("file.separator") + OUTIMPORT_NAME + XML_FILE_EXT;
            f = new File (filepath);
            if (f.exists ())
            {
                f.delete ();
            }
            f.createNewFile ();
            FileWriter out = new FileWriter (f);
            out.write (data);
            out.flush ();
            out.close ();

            return buffer.length ();
        }
        catch (Exception e)
        {
            throw new MSException ("Error in createInstallXMLFile " + e.getMessage ());
        }
    }

    /**
     * To create a install.xml file
     * 
     * @param srcString
     * @param destPath
     * @param component
     * @param version
     * @return the number of bytes wrote to install file
     * @throws MSException
     */
    private int generateInstallXMLFile (String[] srcString, String destPath, String component, String version)
                                                                                                              throws MSException
    {
        String systemhln = "1";
        String customerhln = "1.2";

        try
        {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance ();
            dbf.setIgnoringComments (true);
            DocumentBuilder db = dbf.newDocumentBuilder ();

            StringBuilder buff = new StringBuilder ();
            Map <CfgXMLGroup, List <CfgXMLParameter>> groups = new HashMap <CfgXMLGroup, List <CfgXMLParameter>> ();
            for (int i = 0; i < srcString.length; i++)
            {
                StringBuilder buffer = new StringBuilder (srcString[i]);
                ByteArrayInputStream bis = new ByteArrayInputStream (buffer.toString ().getBytes ("UTF-8"));
                Document dom = db.parse (bis);
                Element el = dom.getDocumentElement ();

                String name = getNodeValue (el, NAME_ELEMENT);
                String[] desc = getNodeDescriptionValue (el, DESC_ELEMENT);
                buff.append (SPACE4).append (makeStartElement (COMMAND_ELEMENT)).append ("\n");
                buff.append (addGroup (systemhln, name, desc));

                List <CfgXMLParameter> parameters = new ArrayList <CfgXMLParameter> ();
                NodeList params = el.getElementsByTagName (PARAMETER_ELEMENT);
                for (int j = 0; j < params.getLength (); j++)
                {
                    Node nd = params.item (j);
                    String pname = getNodeValue (nd, NAME_ELEMENT);
                    String[] pdesc = getNodeDescriptionValue (nd, DESC_ELEMENT);
                    String pvalue = getNodeValue (nd, VALUE_ELEMENT);

                    String pcusFlag = getNodeValue (nd, CUSTOMER_FLAG);
                    String mutability = getNodeValue (nd, MUTABILITY_ELEMENT);
                    String encryptFlag = getNodeValue (nd, ENCRYPTED_FLAG);
                    String[] rules = getNodeRules (nd, RULES_ELEMENT);
                    List <String> options = getNodeOptions (nd, OPTIONS_ELEMENT);
                    long min = 0;
                    long max = 0;
                    if (rules.length == 3)
                    {
                        min = Long.parseLong (rules[1]);
                        max = Long.parseLong (rules[2]);
                    }

                    // add parameter
                    String group = name;

                    customerhln = (pcusFlag.equalsIgnoreCase (BOOLEAN_TRUE)) ? "1.2" : "1";
                    StringBuilder param = addParameter (customerhln, group, pname, pvalue, pdesc, pcusFlag, mutability,
                                                        encryptFlag, rules[0], min, max, options);
                    buff.append (param).append ("\n");

                }
                buff.append (SPACE4).append (makeEndElement (COMMAND_ELEMENT)).append ("\n").append ("\n");

                if (parameters.size () > 0)
                {
                    CfgXMLGroup xgroup = new CfgXMLGroup (name, desc);
                    groups.put (xgroup, parameters);
                }
            }

            // now generate the completed install.xml file
            // file header
            StringBuilder buffer = new StringBuilder ();
            buffer.append ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>").append ("\n");
            buffer.append (getCopyRight ()).append ("\n");

            buffer.append (makeFirstCommandElement (COMMAND_ELEMENT, component, version)).append ("\n");

            buffer.append (buff);

            buffer.append (makeEndElement (COMMAND_ELEMENT)).append ("\n");

            String data = convertCDATADescription (buffer.toString ());

            // write to destination folder
            File f = new File (destPath);
            if (!f.exists ())
            {
                f.mkdir ();
            }

            String filepath = destPath + System.getProperty ("file.separator") + OUTFILE_NAME + XML_FILE_EXT;
            f = new File (filepath);
            if (f.exists ())
            {
                f.delete ();
            }
            f.createNewFile ();
            FileWriter out = new FileWriter (f);
            out.write (data);
            out.flush ();
            out.close ();

            return buffer.length ();
        }
        catch (Exception e)
        {
            throw new MSException ("Error in createInstallXMLFile " + e.getMessage ());
        }
    }

    /**
     * Add the group command xml
     * 
     * @param hln
     * @param name
     * @param desc
     * @return The group xml
     */
    private StringBuilder addGroup (String hln, String name, String[] desc)
    {
        StringBuilder buff = new StringBuilder ();
        buff.append (SPACE8).append (makeElement (ACTION_ELEMENT, ACTION_ADD)).append ("\n");
        buff.append (SPACE8).append (makeElement (TYPE_ELEMENT, GROUP_ELEMENT)).append ("\n");
        buff.append (SPACE8).append (makeElement (NAME_ELEMENT, name)).append ("\n");
        if (hln != null)
        {
            buff.append (SPACE8).append (makeElement (HLN_ELEMENT, hln)).append ("\n");
        }
        buff.append (SPACE8).append (makeStartElement (DESC_ELEMENT)).append ("\n");
        buff.append (SPACE12).append (makeElement (TITLE_ELEMENT, desc[0])).append ("\n");
        buff.append (SPACE12).append (makeElement (BODY_ELEMENT, desc[1])).append ("\n");
        buff.append (SPACE8).append (makeEndElement (DESC_ELEMENT)).append ("\n");
        return buff;
    }

    /**
     * Add the parameter xml
     * 
     * @param hln
     * @param group
     * @param parameterName
     * @param parameterValue
     * @param desc
     * @param custFlag
     * @param mutability
     * @param encrypted
     * @param ruleType
     * @param min
     * @param max
     * @param options
     * @return parameter xml
     */
    private StringBuilder addParameter (String hln, String group, String parameterName, String parameterValue,
                                        String[] desc, String custFlag, String mutability, String encrypted,
                                        String ruleType, long min, long max, List <String> options)
    {
        StringBuilder buff = new StringBuilder ();
        buff.append (SPACE8).append (makeStartElement (COMMAND_ELEMENT)).append ("\n");
        buff.append (SPACE12).append (makeElement (ACTION_ELEMENT, ACTION_ADD)).append ("\n");
        buff.append (SPACE12).append (makeElement (TYPE_ELEMENT, PARAMETER_ELEMENT)).append ("\n");
        buff.append (SPACE12).append (makeElement (NAME_ELEMENT, parameterName)).append ("\n");

        if (parameterValue != null)
        {
            buff.append (SPACE12).append (makeCDataElement (VALUE_ELEMENT, parameterValue)).append ("\n");
        }

        if (hln != null)
        {
            buff.append (SPACE12).append (makeElement (HLN_ELEMENT, hln)).append ("\n");
        }
        buff.append (SPACE12).append (makeElement (GROUP_ELEMENT, group)).append ("\n");

        if (custFlag != null)
        {
            buff.append (SPACE12).append (makeElement (CUSTOMER_FLAG, custFlag)).append ("\n");
        }

        // If mutability is present use it otherwise use standard
        if (StringUtil.isNullOrBlank (mutability))
        {
            mutability = STANDARD_MUTABILITY;
        }
        buff.append (SPACE12).append (makeElement (MUTABILITY_ELEMENT, mutability)).append ("\n");

        buff.append (SPACE12).append (makeElement (ENCRYPTED_FLAG, encrypted)).append ("\n");
        buff.append (SPACE12).append (makeStartElement (RULES_ELEMENT)).append ("\n");
        if (ruleType.equalsIgnoreCase (NUMBER_ELEMENT))
        {
            buff.append (SPACE12).append (makeStartElementWithAttribute (RULE_ELEMENT, TYPE_ELEMENT, ruleType,
                                                                         ATTR_MIN, Long.toString (min), ATTR_MAX,
                                                                         Long.toString (max)));
            buff.append (makeEndElement (RULE_ELEMENT)).append ("\n");
        }
        else
        {
            buff.append (SPACE12).append (makeStartElementWithAttribute (RULE_ELEMENT, TYPE_ELEMENT, ruleType));
            buff.append (makeEndElement (RULE_ELEMENT)).append ("\n");
        }
        buff.append (SPACE12).append (makeEndElement (RULES_ELEMENT)).append ("\n");

        if (options != null && options.size () > 0)
        {
            buff.append (SPACE12).append (makeStartElement (OPTIONS_ELEMENT)).append ("\n");
            for (Iterator <String> itr = options.iterator (); itr.hasNext ();)
            {
                buff.append (SPACE16).append (makeElement (OPTION_ELEMENT, (String) itr.next ())).append ("\n");
            }
            buff.append (SPACE12).append (makeEndElement (OPTIONS_ELEMENT)).append ("\n");
        }
        buff.append (SPACE12).append (makeStartElement (DESC_ELEMENT)).append ("\n");
        buff.append (SPACE16).append (makeElement (TITLE_ELEMENT, desc[0])).append ("\n");
        buff.append (SPACE16).append (makeElement (BODY_ELEMENT, desc[1])).append ("\n");
        buff.append (SPACE12).append (makeEndElement (DESC_ELEMENT)).append ("\n");
        buff.append (SPACE8).append (makeEndElement (COMMAND_ELEMENT));
        return buff;

    }

    /**
     * Returns the node options
     * 
     * @param node
     * @param tag
     * @return Options node
     * @throws MSException
     */
    private List <String> getNodeOptions (Node node, String tag) throws MSException
    {
        List <String> str = new ArrayList <String> ();

        NodeList nl = node.getChildNodes ();
        for (int x = 0; x < nl.getLength (); x++)
        {
            Node e1 = nl.item (x);
            // find the target node
            if (e1.getNodeName ().equalsIgnoreCase (tag))
            {
                Node n = null;
                NodeList child = e1.getChildNodes ();

                if (child.getLength () > 1)
                {
                    for (int i = 0; i < child.getLength (); i++)
                    {
                        n = child.item (i);
                        if (n.getNodeType () == org.w3c.dom.Node.ELEMENT_NODE)
                        {
                            str.add (n.getFirstChild ().getNodeValue ());
                        }
                    }
                }
                else if (child.getLength () == 1)
                {
                    str.add (e1.getFirstChild ().getNodeValue ());
                }
                else
                {
                    return null;
                }

                return str;
            }
        }
        return str;
    }

    /**
     * Returns the node rules
     * 
     * @param node
     * @param tag
     * @return rules node
     * @throws MSException
     */
    private String[] getNodeRules (Node node, String tag) throws MSException
    {
        String[] ret = new String[]
        {};
        NodeList nl = node.getChildNodes ();
        for (int x = 0; x < nl.getLength (); x++)
        {
            Node e1 = nl.item (x);
            // find the target node
            if (e1.getNodeName ().equalsIgnoreCase (tag))
            {
                Node n = null;
                NodeList child = e1.getChildNodes ();
                if (child.getLength () > 1)
                {
                    for (int i = 0; i < child.getLength (); i++)
                    {
                        n = child.item (i);
                        if (n.getNodeType () == org.w3c.dom.Node.ELEMENT_NODE)
                        {
                            break;
                        }
                    }
                }
                else if (child.getLength () == 1)
                {
                    n = e1.getFirstChild ();
                }
                else
                {
                    return ret;
                }
                NamedNodeMap map = n.getAttributes ();
                String type = map.getNamedItem (TYPE_ELEMENT).getNodeValue ();
                if (type.equalsIgnoreCase (NUMBER_ELEMENT))
                {
                    ret = new String[3];
                    ret[0] = type;
                    ret[1] = map.getNamedItem (ATTR_MIN).getNodeValue ();
                    ret[2] = map.getNamedItem (ATTR_MAX).getNodeValue ();
                }
                else
                {
                    ret = new String[1];
                    ret[0] = type;
                }
            }
        }
        return ret;
    }

    /**
     * Returns an array of the description title and body
     * 
     * @param pnode
     * @param tag
     * @return title and body
     */
    private String[] getNodeDescriptionValue (Node pnode, String tag)
    {
        NodeList nl = pnode.getChildNodes ();
        String[] ret = new String[2];
        ret[0] = "";
        ret[1] = "";
        for (int x = 0; x < nl.getLength (); x++)
        {
            Node e1 = nl.item (x);
            if (e1.getNodeName ().equalsIgnoreCase (tag))
            {
                // Found description
                ret[0] = getNodeValue (e1, TITLE_ELEMENT);
                ret[1] = getNodeValue (e1, BODY_ELEMENT);

            }
        }
        return ret;
    }

    private String convertCDATADescription (String data) throws MSException
    {
        BufferedReader br = null;
        String retStr = "";
        try
        {

            // Read string Line By Line
            br = new BufferedReader (new StringReader (data));

            String strLine;

            while ((strLine = br.readLine ()) != null)
            {
                if (strLine.indexOf (END_BODY_TAG) >= 0)
                {
                    strLine = strLine.replaceFirst (END_BODY_TAG, END_CDATA + END_BODY_TAG);
                }
                if (strLine.indexOf (OPEN_BODY_TAG) >= 0)
                {
                    strLine = strLine.replaceFirst (OPEN_BODY_TAG, OPEN_BODY_TAG + START_CDATA);
                }
                if (strLine.indexOf (END_TITLE_TAG) >= 0)
                {
                    strLine = strLine.replaceFirst (END_TITLE_TAG, END_CDATA + END_TITLE_TAG);
                }
                if (strLine.indexOf (OPEN_TITLE_TAG) >= 0)
                {
                    strLine = strLine.replaceFirst (OPEN_TITLE_TAG, OPEN_TITLE_TAG + START_CDATA);
                }

                retStr = retStr + strLine + "\n";
            }

            return retStr;
        }
        catch (IOException ie)
        {
            throw new MSException ("Error in convertToCDATADescription " + ie.getMessage ());
        }
        finally
        {
            if (br != null)
            {
                try
                {
                    br.close ();
                }
                catch (IOException ioe)
                {
                }
            }
        }
    }

    /**
     * Converts the description to be CDATA format
     * 
     * @param srcFiles
     * @param srcPath
     * @return The data from all of the xml file for converting
     * @throws MSException if any problem to convert the description.
     */
    private String[] convertCDATADescription (String[] srcFiles, String srcPath) throws MSException
    {
        String[] retS = new String[srcFiles.length];
        FileInputStream fin = null;
        DataInputStream in = null;
        BufferedReader br = null;

        try
        {
            for (int i = 0; i < srcFiles.length; i++)
            {
                // Read File Line By Line
                String path = srcPath + System.getProperty ("file.separator") + srcFiles[i];
                fin = new FileInputStream (path);
                in = new DataInputStream (fin);
                br = new BufferedReader (new InputStreamReader (in, "UTF-8"));

                String strLine;
                String retStr = "";
                while ((strLine = br.readLine ()) != null)
                {
                    if (strLine.indexOf (END_BODY_TAG) >= 0)
                    {
                        strLine = strLine.replaceFirst (END_BODY_TAG, END_CDATA + END_BODY_TAG);
                    }
                    if (strLine.indexOf (OPEN_BODY_TAG) >= 0)
                    {
                        strLine = strLine.replaceFirst (OPEN_BODY_TAG, OPEN_BODY_TAG + START_CDATA);
                    }
                    retStr = retStr + strLine;
                }
                retS[i] = retStr;
            }
            return retS;
        }
        catch (FileNotFoundException fe)
        {
            throw new MSException ("Unable to find file");
        }
        catch (IOException ie)
        {
            throw new MSException ("Error in convertToCDATADescription " + ie.getMessage ());
        }
        finally
        {
            if (br != null)
            {
                try
                {
                    br.close ();
                }
                catch (IOException ioe)
                {
                }
            }
            if (in != null)
            {
                try
                {
                    in.close ();
                }
                catch (IOException ioe)
                {
                }
            }
            if (fin != null)
            {
                try
                {
                    fin.close ();
                }
                catch (IOException ioe)
                {
                }
            }

        }
    }

    private String[] getSrcXMLFileName (String srcPath) throws MSException
    {
        String[] ret = new String[]
        {};
        List <String> fs = new ArrayList <String> ();

        File f = new File (srcPath);
        String[] files = f.list ();
        if (files != null)
        {

            for (int i = 0; i < files.length; i++)
            {
                if (files[i].endsWith (XML_FILE_EXT))
                {
                    fs.add (files[i]);
                }
            }
        }
        else
        {
            throw new MSException ("Unable to find any <property>.xml file at srcPath=" + srcPath);
        }
        ret = new String[fs.size ()];
        fs.toArray (ret);
        return ret;
    }

    private String makeFirstCommandElement (String elementName)
    {
        StringBuilder bb = new StringBuilder ();
        bb.append (OPENING_TAG).append (elementName).append (ENDING_TAG);
        return bb.toString ();
    }

    private String makeFirstCommandElement (String elementName, String component, String version)
    {
        StringBuilder bb = new StringBuilder ();
        bb.append (OPENING_TAG).append (elementName).append (" ").append (COMPONENT_ATTR).append ("=\'")
          .append (component);
        bb.append ("\' ").append (VERSION_ATTR).append ("=\'").append (version).append ("\' ").append (ENDING_TAG);
        return bb.toString ();
    }

    private String makeStartElement (String elementName)
    {
        StringBuilder buff = new StringBuilder ();
        buff.append (OPENING_TAG).append (elementName).append (ENDING_TAG);
        return buff.toString ();
    }

    private String makeCDataElement (String elementName, String value)
    {
        StringBuilder buff = new StringBuilder ();
        buff.append (makeStartElement (elementName));
        buff.append (START_CDATA);
        buff.append (value);
        buff.append (END_CDATA);
        buff.append (makeEndElement (elementName));
        return buff.toString ();
    }

    private String makeElement (String elementName, String value)
    {
        StringBuilder buff = new StringBuilder ();
        buff.append (makeStartElement (elementName));
        buff.append (value);
        buff.append (makeEndElement (elementName));
        return buff.toString ();
    }

    private String makeEndElement (String elementName)
    {
        StringBuilder buff = new StringBuilder ();
        buff.append (CLOSING_TAG).append (elementName).append (ENDING_TAG);
        return buff.toString ();
    }

    private String makeStartElementWithAttribute (String elementName, String attrName, String attrValue,
                                                  String attrName1, String attrValue1, String attrName2,
                                                  String attrValue2)
    {
        StringBuilder buff = new StringBuilder ();
        buff.append (OPENING_TAG).append (elementName);
        buff.append (" ");
        buff.append (attrName).append ("=");
        buff.append ("\"").append (attrValue).append ("\" ");
        buff.append (attrName1).append ("=");
        buff.append ("\"").append (attrValue1).append ("\" ");
        buff.append (attrName2).append ("=");
        buff.append ("\"").append (attrValue2).append ("\" ");
        buff.append (ENDING_TAG);
        return buff.toString ();
    }

    private String makeStartElementWithAttribute (String elementName, String attrName, String attrValue)
    {
        StringBuilder buff = new StringBuilder ();
        buff.append (OPENING_TAG).append (elementName);
        buff.append (" ");
        buff.append (attrName).append ("=");
        buff.append ("\"").append (attrValue).append ("\"");
        buff.append (ENDING_TAG);
        return buff.toString ();
    }

    private StringBuilder getCopyRight ()
    {
        StringBuilder buff = new StringBuilder ();

        buff.append ("<!-- ").append ("\n");
        buff.append ("/* Copyright ( c ) 2011 Pinfly Corporation. All Rights Reserved.").append ("\n");
        buff.append ("*").append ("\n");
        buff.append ("* This software is the confidential and proprietary information of Pinfly").append ("\n");
        buff.append ("* Corporation (\"Confidential Information\").  You shall not disclose such").append ("\n");
        buff.append ("* Confidential Information and shall use it only in accordance with the terms").append ("\n");
        buff.append ("* of the license agreement you entered into with Pinfly Corporation or a Pinfly").append ("\n");
        buff.append ("* authorized reseller (the \"License Agreement\"). Pinfly may make changes to the").append ("\n");
        buff.append ("* Confidential Information from time to time. Such Confidential Information may").append ("\n");
        buff.append ("* contain errors.").append ("\n");
        buff.append ("*").append ("\n");
        buff.append ("* EXCEPT AS EXPLICITLY SET FORTH IN THE LICENSE AGREEMENT, PINFLY DISCLAIMS ALL").append ("\n");
        buff.append ("* WARRANTIES, COVENANTS, REPRESENTATIONS, INDEMNITIES, AND GUARANTEES WITH").append ("\n");
        buff.append ("* RESPECT TO SOFTWARE AND DOCUMENTATION, WHETHER EXPRESS OR IMPLIED, WRITTEN OR").append ("\n");
        buff.append ("* ORAL, STATUTORY OR OTHERWISE INCLUDING, WITHOUT LIMITATION, THE IMPLIED").append ("\n");
        buff.append ("* WARRANTIES OF MERCHANTABILITY, TITLE, NON-INFRINGEMENT AND FITNESS FOR A").append ("\n");
        buff.append ("* PARTICULAR PURPOSE. PINFLY DOES NOT WARRANT THAT END USER'S USE OF THE").append ("\n");
        buff.append ("* SOFTWARE WILL BE UNINTERRUPTED, ERROR FREE OR SECURE.").append ("\n");
        buff.append ("*").append ("\n");
        buff.append ("* PINFLY SHALL NOT BE LIABLE TO END USER, OR ANY OTHER PERSON, CORPORATION OR").append ("\n");
        buff.append ("* ENTITY FOR INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL").append ("\n");
        buff.append ("* DAMAGES, OR DAMAGES FOR LOSS OF PROFITS, REVENUE, DATA OR USE, WHETHER IN AN").append ("\n");
        buff.append ("* ACTION IN CONTRACT, TORT OR OTHERWISE, EVEN IF PINFLY HAS BEEN ADVISED OF THE").append ("\n");
        buff.append ("* POSSIBILITY OF SUCH DAMAGES. PINFLY' TOTAL LIABILITY TO END USER SHALL NOT").append ("\n");
        buff.append ("* EXCEED THE AMOUNTS PAID FOR THE PINFLY SOFTWARE BY END USER DURING THE PRIOR").append ("\n");
        buff.append ("* TWELVE (12) MONTHS FROM THE DATE IN WHICH THE CLAIM AROSE.  BECAUSE SOME").append ("\n");
        buff.append ("* STATES OR JURISDICTIONS DO NOT ALLOW LIMITATION OR EXCLUSION OF CONSEQUENTIAL").append ("\n");
        buff.append ("* OR INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO END USER.").append ("\n");
        buff.append ("*").append ("\n");
        buff.append ("* Copyright version 2.0").append ("\n");
        buff.append ("*/").append ("\n");
        buff.append ("-->").append ("\n");
        return buff;
    }

    /**
     * To be an entry for build process to create the install.xml which include
     * all of xml files in the srcPath
     * 
     * @param args
     */
    public static void main (String[] args)
    {
        if (args.length != 4)
        {
            System.out.println ("Usage: srcPath destPath component version.");
            System.exit (0);
        }

        File f = new File (args[0]);
        if (!f.isDirectory ())
        {
            usage ("You must provide the directory for srcPath.");
        }
        f = new File (args[1]);
        if (!f.isDirectory ())
        {
            usage ("You must provide the directory for destPath.");
        }
        if (StringUtil.isNullOrBlank (args[2]))
        {
            usage ("You must provide the component name.");
        }
        if (StringUtil.isNullOrBlank (args[3]))
        {
            usage ("You must provide the version.");
        }

        try
        {
            if (args.length == 4)
            {
                PfyXMLUtil.getInstance ().createInstallXMLFile (args[0], args[1], args[2], args[3]);
            }
            else
            {
                usage ("You must provide the correct argument.");
            }
        }
        catch (MSException e)
        {
            System.out.println (e.getMessage ());
            System.exit (1);
        }

    }

    private static void usage (String error)
    {
        String x = "Usage: srcPath destPath component version "
                   + LS
                   + "Where:"
                   + LS
                   + "srcPath The directory where property files to convert are placed."
                   + LS
                   + "     Note: All of the original property files in this directory will be converted."
                   + LS
                   + LS
                   + "destPath Where the converted configuration xml properties file is placed."
                   + LS
                   + LS
                   + "component The component name for which the converted configuration xml properties file belongs to."
                   + LS + LS
                   + "version The version number associated with the converted configuration xml properties file." + LS
                   + LS;

        System.out.println ("ERROR: " + error + "" + LS + LS + x);
        System.exit (-1);
    }

}

class CfgXMLParameter
{
    private String m_parameterName;
    private String m_parameterValue;
    private String m_desc;
    private String m_custFlag;
    private String m_encrypted;
    private String m_ruleType;
    private long m_min;
    private long m_max;
    private List <String> m_options;

    CfgXMLParameter (String name, String value, String desc, String custFlag, String encrypted, String type, long min,
                     long max, List <String> ops)
    {
        m_parameterName = name;
        m_parameterValue = value;
        m_desc = desc;
        m_custFlag = custFlag;
        m_encrypted = encrypted;
        m_ruleType = type;
        m_min = min;
        m_max = max;
        m_options = ops;
    }

    /**
     * @return the m_parameterName
     */
    String getParameterName ()
    {
        return m_parameterName;
    }

    /**
     * @return the m_parameterValue
     */
    String getParameterValue ()
    {
        return m_parameterValue;
    }

    /**
     * @return the m_desc
     */
    String getDesc ()
    {
        return m_desc;
    }

    /**
     * @return the m_custFlag
     */
    String getCustFlag ()
    {
        return m_custFlag;
    }

    /**
     * @return the m_encrypted
     */
    String getEncrypted ()
    {
        return m_encrypted;
    }

    /**
     * @return the m_ruleType
     */
    String getRuleType ()
    {
        return m_ruleType;
    }

    /**
     * @return the m_min
     */
    long getMin ()
    {
        return m_min;
    }

    /**
     * @return the m_max
     */
    long getMax ()
    {
        return m_max;
    }

    /**
     * @return the Options
     */
    List <String> getOptions ()
    {
        return m_options;
    }
}

class CfgXMLGroup
{
    private String m_groupName;
    private String[] m_description;

    CfgXMLGroup (String name, String[] desc)
    {
        m_groupName = name;
        m_description = desc;
    }

    String getGroupName ()
    {
        return m_groupName;
    }

    String[] getDescription ()
    {
        return m_description;
    }
}
